﻿using LeRenard;
using System;
using System.Collections;
using System.Drawing;
using System.Windows.Forms;
namespace LeRenard.Libraries.Drawing.GdiPlus.Forms
{
    public abstract class GdiPlusForm : Form, IGdiPlusForm
    {
        /// <summary>
        /// Holds the the backbuffer.
        /// </summary>
        private readonly IBuffer _buffer;

        /// <summary>
        /// Holds the backbuffer target.
        /// </summary>
        private readonly ITarget _target;

        /// <summary>
        /// Holds double buffering context.
        /// </summary>
        private BufferedGraphicsContext _bufferedGraphicsContext;

        /// <summary>
        /// Holds double buffer graphics.
        /// </summary>
        private BufferedGraphics _bufferedGraphics;

        /// <summary>
        /// Holds the timer for the drawing / rendering loop.
        /// </summary>
        private Timer _renderLoop;

        /// <summary>
        /// Holds the time for measuring frames per second.
        /// </summary>
        private Timer _fpsTimer;    
    
        /// <summary>
        /// Holds the intermediate counter for fps.
        /// </summary>
        private int _fpsCounter;

        /// <summary>
        /// Gets the frames per second.
        /// </summary>
        public int Fps { get { return _fps; } }
        private int _fps;

        /// <summary>
        /// Determines whether to show FPS counter or not.
        /// </summary>        
        public bool ShowFps { get { return _showFps; } set { _showFps = true; } }
        private bool _showFps = false;

        /// <summary>
        /// Determines whether to show console or not.
        /// </summary>
        public bool ShowConsole { get { return _showConsole; } set { _showConsole = true; } }
        private bool _showConsole = false;

        /// <summary>
        /// Holds the console text.
        /// </summary>
        private readonly LimitedList<string> _consoleBuffer;

        /// <summary>
        /// Gets/sets the font for the console.
        /// </summary>
        public Font Font 
        {
            get { return _font; }
            set { _font = value; }
        }
        private Font _font;
        private SolidBrush _fontBrush;

        /// <summary>
        /// Gets/sets the color of the font for the console.
        /// </summary>
        public Color Color 
        {
            get { return _fontBrush.Color; }
            set { _fontBrush = new SolidBrush(value); }
        }        

        #region Construction
        public GdiPlusForm(Size size)
        {
            this.StartPosition = FormStartPosition.CenterScreen;
            this.FormBorderStyle = FormBorderStyle.FixedSingle;
            this.ClientSize = size;

            this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.GdiPlusForm_FormClosing);
            this.ResizeEnd += GdiPlusForm_ResizeEnd;

            _buffer = new Buffer(this.ClientSize);
            GraphicsOptimizer.OptimizeBuffer(_buffer);
            _target = new Target(this.CreateGraphics());
            GraphicsOptimizer.OptimizeTarget(_target);

            WindowsFormsOptimizer.Optimize(this);

            this.Font = new Font("Segoe Ui Light", 8, FontStyle.Bold);
            this.Color = Color.FromArgb(120, Color.Aqua);

            _consoleBuffer = new LimitedList<string>(8);

            _fpsTimer = new System.Windows.Forms.Timer() { Interval = 1000, Enabled = true };
            _fpsTimer.Tick += _fpsTimer_Tick;

            _renderLoop = new System.Windows.Forms.Timer() { Interval = 1, Enabled = true };
            _renderLoop.Tick += _renderLoop_Tick;
            RenderLoop();
        }
        
        private void _renderLoop_Tick(object sender, EventArgs e)
        {
            RenderLoop();
        }

        private void GdiPlusForm_ResizeEnd(object sender, EventArgs e)
        {
            _renderLoop.Enabled = false;

            _buffer.Resize(this.ClientSize);
            _target.ReAllocate(this.CreateGraphics());

            if (_bufferedGraphicsContext != null) {
                _bufferedGraphicsContext.Dispose();
                _bufferedGraphicsContext = null;
            }

            if (_bufferedGraphics != null) {
                _bufferedGraphics.Dispose();
                _bufferedGraphics = null;
            }

            _renderLoop.Enabled = true;
        }

        private void _fpsTimer_Tick(object sender, EventArgs e)
        {
            _fps = _fpsCounter;
            _fpsCounter = 0;            
        }

        private void GdiPlusForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (_buffer != null)
                _buffer.Dispose();

            if (_fpsTimer != null) {
                _fpsTimer.Tick += _fpsTimer_Tick;
                _fpsTimer.Enabled = false;
                _fpsTimer.Dispose();
            }

            if (_target != null)
                _target.Dispose();
        }
        #endregion

        private void RenderLoop()
        {
            if (!Disposing && !IsDisposed) {
                // Check if there's a context
                if (_bufferedGraphicsContext == null) {
                    _bufferedGraphicsContext = BufferedGraphicsManager.Current;
                    if (_bufferedGraphics != null) {
                        _bufferedGraphics.Dispose();
                        _bufferedGraphics = null;
                    }
                }

                // Check if there's a buffer
                if (_bufferedGraphics == null)
                    _bufferedGraphics = _bufferedGraphicsContext.Allocate(_target.Graphics, this.ClientRectangle);

                try {
                    // Clear the target, and buffer                                
                    _buffer.Clear(Color.Black);
                    _bufferedGraphics.Graphics.Clear(Color.Black);

                    // Raise the paint event where the drawing will go.
                    OnPaint(_buffer);
                    _bufferedGraphics.Graphics.DrawImageUnscaled(_buffer.Render(), 0, 0);

                    // Handle FPS (one frame rendered)
                    _fpsCounter++;

                    // Update the window using the fastest GDI+ function to do it with
                    _bufferedGraphics.Render();
                } catch (Exception ex) {
                    // TODO: Log
                }
            }
        }

        private void OnPaint(IBuffer buffer)
        {
            Draw(buffer);

            if (ShowFps) {
                DrawFps(buffer);
            }
            if (ShowConsole) {
                DrawConsole(buffer);
            }
        }

        private void DrawFps(IBuffer buffer)
        {
            var fpsText = String.Format("FPS: {0}.", this.Fps.ToString("000"));
            var size = buffer.Graphics.MeasureString(fpsText, _font);
            var x = buffer.Size.Width - 2 - size.Width;
            var y = 2;
            this.DrawString(buffer, fpsText, x, y);
        }

        private void DrawConsole(IBuffer buffer)
        {
            var i = 1;
            foreach (var consoleLine in _consoleBuffer.Items) {
                var size = buffer.Graphics.MeasureString(consoleLine, _font);
                var x = buffer.Size.Width - 2 - size.Width;
                var y = buffer.Size.Height - 2 - (size.Height * i);
                this.DrawString(buffer, consoleLine, x, y);

                i++;
            }
        }

        public abstract void Draw(IBuffer buffer);

        /// <summary>
        /// Draws a string.
        /// </summary>
        /// <param name="string">The string to draw.</param>        
        /// <param name="x">The x-coordinate of the upper left corner.</param>
        /// <param name="y">The y-coordinate of the upper left corner.</param>
        /// <param name="font">The font to use, if null the default font will be used.</param>
        /// /// <param name="font">The brush to use, if null the default brush will be used.</param>
        public void DrawString(IBuffer buffer, string @string, float x, float y, Font font = null, SolidBrush brush = null)
        {
            var g = buffer.Graphics;
            var f = font == null ? _font : font;
            var b = brush == null ? _fontBrush : brush;            

            g.DrawString(@string, f, b, x, y);
        }

        /// <summary>
        /// Writes a message to the console.
        /// </summary>        
        public void LogToConsole(string message)
        {
            var consoleMessage = "[{0}] {1}".FormatWith(
                    DateTime.Now.ToLongTimeString(),
                    message
                );

            _consoleBuffer.Add(consoleMessage);
        }
    }
}
